package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;


 /*  @Override
   public synchronized void testLock() {
      // 查询Redis中的num值
      String value = (String)this.redisTemplate.opsForValue().get("num");
      // 没有该值return
      if (StringUtils.isBlank(value)){
         return ;
      }
      // 有值就转成成int
      int num = Integer.parseInt(value);
      // 把Redis中的num值+1
      this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
   }*/


    /**
     * 使用SpringDataRedis实现分布式锁-要求，业务场景每个线程都得完成对num值+1
     * ①版本一，采用Redis命令-setnx命令+expire命令  问题：产生死锁 解决：set key val px nx
     */
    /*@Override
    public void testLock() {
        try {
            //一、先尝试获取分布式锁 setnx+expire
            String lockKey = "lock";
            String lockValue = UUID.randomUUID().toString().replaceAll("-", "");
            //①版本一，采用Redis命令-setnx命令+expire命令
            //Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue);
            //    redisTemplate.expire(lockKey, 5, TimeUnit.SECONDS);
            //②版本二：采用Redis命令set ex nx
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
            if (flag) {
                try {
                    //获取分布式锁成功，执行业务逻辑
                    // 查询Redis中的num值
                    String value = (String) this.redisTemplate.opsForValue().get("num");
                    // 没有该值return
                    if (StringUtils.isBlank(value)) {
                        return;
                    }
                    // 有值就转成成int
                    int num = Integer.parseInt(value);
                    // 把Redis中的num值+1
                    this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
                } finally {
                    //执行业务完毕后将锁释放
                    //redisTemplate.delete(lockKey);
                    //版本③：释放锁加入判断，必须保证当前线程锁的值跟Redis中锁的值一致 本质：判断跟删除不具备原子性
                    *//*if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {//线程一：判断通过后 时间片耗尽+锁正好过期，锁自动释放
                        redisTemplate.delete(lockKey); //线程一删除了其他线程锁
                    }*//*
                    //版本④：释放锁采用lua脚本，保证判断跟删除原子性
                    String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    //1.创建封装lua脚本对象
                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                    script.setScriptText(scriptText);
                    script.setResultType(Long.class);
                    //2.设置键跟入参的值
                    //3.执行脚本
                    redisTemplate.execute(script, Arrays.asList(lockKey), lockValue);
                }
            } else {
                //获取锁失败，增加“自旋”逻辑
                TimeUnit.MILLISECONDS.sleep(500);
                this.testLock();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }*/

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 采用Redisson框架实现分布式锁
     */
    @Override
    public void testLock() {
        try {
            //一、先获取分布式锁
            RLock myLock = redissonClient.getLock("myLock");
            //阻塞到获取锁成功为止
            //myLock.lock();
            //lock(锁过期时间，时间单位) 如果业务执行大于锁过期时间，违反锁排他性
            //myLock.lock(3, TimeUnit.SECONDS);
            //tryLock(等待时间，时间单位) 如果在等待时间内没有获取到锁返回false,反之返回true
            //boolean falg = myLock.tryLock(3, TimeUnit.SECONDS);
            //tryLock(等待时间，锁的时间，时间单位) 如果在等待时间内没有获取到锁返回false,反之返回true且通过leaseTime设置锁过期时间
            boolean flag = myLock.tryLock(5, 3, TimeUnit.SECONDS);
            if (flag) {
                try {
                    //当前线程获取锁成功后，尝试继续获取锁
                    this.checkLock();
                    //二、获取锁成功后执行业务
                    // 查询Redis中的num值
                    String value = (String) this.redisTemplate.opsForValue().get("num");
                    // 没有该值return
                    if (StringUtils.isBlank(value)) {
                        return;
                    }
                    // 有值就转成成int
                    int num = Integer.parseInt(value);
                    // 把Redis中的num值+1
                    this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
                } finally {
                    //三、将锁释放
                    myLock.unlock();
                }
            } else {
                //本次获取锁失败
                TimeUnit.MILLISECONDS.sleep(500);
                this.testLock();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    //@Autowired
    //private RedissonClient redissonClient;


    /**
     * 修改数据方法
     */
    @Override
    public void write() {
        //1.获取写锁
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        RLock lock = rwlock.writeLock();
        lock.lock(5, TimeUnit.SECONDS); //阻塞等待一致获取到写锁
        System.out.println("进行写数据操作");
        //2.释放写锁
        //lock.unlock();
    }

    /**
     * 读取数据方法
     */
    @Override
    public void read() {
        //1.获取读锁
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        RLock rLock = rwlock.readLock();
        rLock.lock(5, TimeUnit.SECONDS);
        System.out.println("进行读取数据");
        //2.释放读锁
        //rLock.unlock();
    }

    /**
     * 异步方法，由子线程执行
     *
     * @param data
     */
    @Override
    @Async("customAsyncThreadPool")  //标识该方法为异步方法
    public void async(String data) {
        System.err.println(Thread.currentThread().getName() + "异步任务方法执行了");
    }


    /**
     * 演示分布式锁Redisson是否可重入
     */
    private void checkLock() {
        RLock myLock = redissonClient.getLock("myLock");
        myLock.lock();
        System.out.println("测试可重入");
        myLock.unlock();
    }


}
